/*
 * @Author: ximin.li 
 * @Date: 2020-08-28 10:50:45 
 * @Last Modified by: ximin.li
 * @Last Modified time: 2020-08-28 17:21:54
 * @Description: Create a input/output character device
 */
#define DEBUG
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/uaccess.h>

#include "chardev.h"
#define SUCCESS 0
#define DEVICE_NAME "char_dev"
#define BUF_LEN 80

static int Device_Open = 0;

static char Message[BUF_LEN];

static char *Message_Ptr;

/*
 * 当一个进程尝试打开device 文件时，调用
 */
static int device_open(struct inode *inode, struct file *file)
{
#ifdef DEBUG
  printk(KERN_INFO "device_open(%p)\n", file);
#endif
  if (Device_Open)
  {
    return -EBUSY;
  }
  Device_Open ++;
  Message_Ptr = Message;
  try_module_get(THIS_MODULE);
  return SUCCESS;
}

/*
 * 当进程释放device文件时调用
 */
static int device_release(struct inode *inode, struct file *file)
{
#ifdef DEBUG
  printk(KERN_INFO "device_release(%p,%p)\n", inode, file);
#endif
  Device_Open --;
  module_put(THIS_MODULE);
  return SUCCESS;
}

/*
 * 当进程尝试读取device中的内容时调用
 */
static ssize_t device_read(struct file *file,
                           char __user *buffer,
                           size_t length,
                           loff_t *offset)
{
  int bytes_read=0;
#ifdef DEBUG
  printk(KERN_INFO "device_read(%p,%p,%d)\n", file, buffer, length);
#endif
  if (*Message_Ptr == 0)
  {
    return 0;
  }

  while (length && *Message_Ptr) {
    put_user(*(Message_Ptr++), buffer++);
    length --;
    bytes_read ++;
  }
#ifdef DEBUG
  printk(KERN_INFO "Read %d bytes, %d left\n", bytes_read, length);
#endif
  return bytes_read; // 返回读取的字节数
}

/*
 * 当向device中写入时，调用
 */
static ssize_t
device_write(struct file *file,
             const char __user *buffer,
             size_t length,
             loff_t *offset)
{
  int i;
#ifdef DEBUG
  printk(KERN_INFO "device_write(%p,%s,%d)",file, buffer, length);
#endif
  for (i = 0; i < length && i < BUF_LEN; i++)
  {
    get_user(Message[i], buffer + i);
  }
  Message_Ptr = Message;
  return i;
}

/*
 *  当进程对device file 做io操作时，就会调用该函数
 */
long device_ioctl(struct file *file,
                 unsigned int ioctl_num,
                 unsigned long ioctl_param)
{
  int i;
  char *temp;
  char ch;

  switch(ioctl_num) {
    case IOCTL_SET_MSG:
      /*
       * 接收一个用户空间的Message指针
       * 把这个指针设置到device的消息中
       * 把相关参数直接传到ioctl
       */
      temp = (char *)ioctl_param;
      get_user(ch, temp);
      for (i = 0; ch && i < BUF_LEN; i++, temp++){
        get_user(ch, temp);
      }
      device_write(file, (char *)ioctl_param, i, 0);
      break;
    case IOCTL_GET_MSG:
      /*
       * 将消息传到调用device的进程
       */
      i = device_read(file, (char *)ioctl_param, 99, 0);
      put_user('\0', (char *)ioctl_param + i);
      break;
    case IOCTL_GET_NTH_BYTE:
      return Message[ioctl_param];
      break;
  }

  return SUCCESS;
}

/*
 * 这个结构体保存着device处理事件的动作；
 * 由于devices table 保存着这个结构体，
 * 它就不能声明在init_module中
 * 一些没有实现的函数都是NULL
 */
struct file_operations Fops = {
  .read = device_read,
  .write = device_write,
  .unlocked_ioctl = device_ioctl,
  .open = device_open,
  .release = device_release,
};

/*
 * 初始化module， 注册char device
 */
int init_module()
{
  int ret_val;
  ret_val = register_chrdev(MAJOR_NUM, DEVICE_NAME, &Fops);
  if (ret_val < 0) {
    printk(KERN_ALERT "%s failed with %d\n", 
                      "Sorry, register the charater device", ret_val);
    return ret_val;
  }

  printk(KERN_INFO "%s The Major device number is %d.\n",
         "Registeration is a success,", MAJOR_NUM);
  printk(KERN_INFO "if you want to talk to the device driver,\n");
  printk(KERN_INFO "you'll have to create a device file. \n");
  printk(KERN_INFO "We suggest you use: \n");
  printk(KERN_INFO "mknod %s c %d 0\n", DEVICE_FILE_NAME, MAJOR_NUM);
  printk(KERN_INFO "The device file name is important, because\n");
  printk(KERN_INFO "the ioctl program assumes that's the\n");
  printk(KERN_INFO "file you'll use.\n");
  
  return 0;
}


/*
 * unregister the device
 */
void cleanup_module()
{
  unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
  printk(KERN_ALERT "INFO: unregister_chrdev\n");
}